package net.entelijan.cobean.data.literature.persistence;

import java.util.ArrayList;
import java.util.List;

import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.jws.WebService;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import net.entelijan.cobean.data.literature.ILitService;
import net.entelijan.cobean.data.literature.LitAuthor;
import net.entelijan.cobean.data.literature.LitBook;
import net.entelijan.cobean.data.literature.LitBookSearchItem;
import net.entelijan.cobean.data.literature.OrderAttribute;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

@Stateless
@Local(ILitService.class)
@WebService(portName = "WsLitService", name = "WsLitService", serviceName = "WsLitService")
public class PersistenceLitService implements ILitService {

	private static Log log = LogFactory.getLog(PersistenceLitService.class);

	@PersistenceContext(unitName = "LitUnit")
	private EntityManager entityManager;

	public PersistenceLitService() {
		super();
	}

	@Override
	public void addBookToAuthor(LitAuthor author, LitBook book) {
		LitAuthor a = loadAuthorAttached(author.getId());
		if (book.getId() > 0) {
			LitBook b1 = loadBookAttached(book.getId());
			if (a.getBooks().contains(b1)) {
				log.warn("Cannot add '" + b1
						+ "' because it is already inclosed in '" + a + "'");
			} else {
				b1.setAuthor(a);
				a.getBooks().add(b1);
			}
		} else {
			saveBook(book);
			book.setAuthor(a);
			a.getBooks().add(book);
		}
		entityManager.persist(a);
	}

	@Override
	public void removeBookFromAuthor(LitAuthor author, LitBook book) {
		LitAuthor a = loadAuthorAttached(author.getId());
		LitBook b = loadBookAttached(book.getId());
		if (a.getBooks().contains(b)) {
			a.getBooks().remove(b);
			b.setAuthor(null);
			this.entityManager.persist(a);
		} else {
			log.warn("Cannot remove '" + b + "' from '" + a
					+ "' because it is not contained");
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<LitAuthor> loadAllAuthors() {
		Query q = entityManager
				.createQuery("from net.entelijan.cobean.data.literature.LitAuthor");
		List<LitAuthor> re = q.getResultList();
		for (LitAuthor la : re) {
			la.setBooks(null);
		}
		return re;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<LitBook> loadAllBooks() {
		Query q = entityManager
				.createQuery("from net.entelijan.cobean.data.literature.LitBook");
		List<LitBook> re = q.getResultList();
		return re;
	}

	@Override
	public LitAuthor loadAuthor(long id) {
		LitAuthor re = loadAuthorAttached(id);
		List<LitBook> books = re.getBooks();
		// Load all books (lazy) TODO Find a better way to load the books
		for (@SuppressWarnings("unused")
		LitBook dummy : books) {
			// Just load but do nothing
		}
		// Detache the books otherwise you cannot set the authors 'null' without
		// deleting them
		this.entityManager.clear();
		// Remove the authors to avoid loops in the webservice
		for (LitBook b : books) {
			b.setAuthor(null);
		}
		return re;
	}

	private LitAuthor loadAuthorAttached(long id) {
		Query q = entityManager
				.createQuery("from net.entelijan.cobean.data.literature.LitAuthor where id = :id");
		q.setParameter("id", id);
		LitAuthor re = (LitAuthor) q.getSingleResult();
		return re;
	}

	@Override
	public LitBook loadBook(long id) {
		LitBook re = loadBookAttached(id);
		re.getAuthor();
		this.entityManager.clear();
		if (re.getAuthor() != null) {
			re.getAuthor().setBooks(null);
		}
		return re;
	}

	private LitBook loadBookAttached(long id) {
		Query q = entityManager
				.createQuery("from net.entelijan.cobean.data.literature.LitBook where id = :id");
		q.setParameter("id", id);
		LitBook re = (LitBook) q.getSingleResult();
		return re;
	}

	@Override
	public void saveAuthor(LitAuthor author) {
		if (author.getId() <= 0) {
			author.setId(-1);
			entityManager.persist(author);
		} else {
			entityManager.merge(author);
		}
	}

	@Override
	public void saveBook(LitBook book) {
		if (book.getId() <= 0) {
			book.setId(-1);
			entityManager.persist(book);
		} else {
			entityManager.merge(book);
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<LitBook> findBooks(final LitBookSearchItem searchItem) {
		ClauseBuilder wc = new ClauseBuilder("where", " and",
				new ClauseMapping() {

					@Override
					public List<?> getClauseItems() {
						ArrayList<String> re = new ArrayList<String>();
						if (searchItem.getTitle() != null
								&& !"".equals(searchItem.getAuthor())) {
							re.add("b.title like '"
									+ searchItem.getTitle().trim() + "%'");
						}
						if (searchItem.getYear() != null
								&& !"".equals(searchItem.getYear())) {
							re.add("b.year = " + searchItem.getYear().trim());
						}
						if (searchItem.getAuthor() != null
								&& !"".equals(searchItem.getAuthor())) {
							re.add("b.author.name like '"
									+ searchItem.getAuthor().trim() + "%'");
						}
						return re;
					}
				});
		String qlString = "from LitBook b " + wc.buildClause();
		log.info("[findBooks] qlString='" + qlString + "'");
		Query q = entityManager.createQuery(qlString);
		List<LitBook> re = q.getResultList();
		cutResult(re);
		return re;

	}

	@Override
	public long getBookCount() {
		Query q = entityManager.createQuery("select count(*) from LitBook");
		Long re = (Long) q.getSingleResult();
		return re;
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<LitBook> loadBooks(int fromIndex, int toIndex,
			final List<OrderAttribute> order) {
		List<LitBook> re;
		if (fromIndex >= toIndex) {
			log.warn("Illegal ranges " + fromIndex + " to " + toIndex);
			re = new ArrayList<LitBook>();
		} else {
			ClauseBuilder orderClause = new ClauseBuilder("order by", ",",
					new IterableClauseMapping<OrderAttribute>(order) {

						@Override
						protected Object map(OrderAttribute elem) {
							String re = null;
							if (elem == OrderAttribute.AUTHOR_ASC) {
								re = "b.author.name ASC";
							} else if (elem == OrderAttribute.TITLE_ASC) {
								re = "b.title ASC";
							} else if (elem == OrderAttribute.YEAR_ASC) {
								re = "b.year ASC";
							} else if (elem == OrderAttribute.AUTHOR_DESC) {
								re = "b.author.name DESC";
							} else if (elem == OrderAttribute.TITLE_DESC) {
								re = "b.title DESC";
							} else if (elem == OrderAttribute.YEAR_DESC) {
								re = "b.year DESC";
							}
							return re;
						}

					});

			String qlString = "from LitBook b " + orderClause.buildClause();
			log.info("[loadBooks] qlString='" + qlString + "'");
			Query q = entityManager.createQuery(qlString);
			q.setFirstResult(fromIndex);
			q.setMaxResults(toIndex - fromIndex);
			re = q.getResultList();
		}
		cutResult(re);
		return re;
	}

	private void cutResult(List<LitBook> re) {
		log.info("[cutResult] n=" + re.size());
		for (LitBook b : re) {
			if (b != null) {
				b.getAuthor();
			}
		}
		this.entityManager.clear();
		for (LitBook b : re) {
			if (b != null) {
				LitAuthor a = b.getAuthor();
				if (a != null) {
					a.setBooks(null);
				}
			}
		}
	}

	@Override
	public void deleteAuthor(long id) {
		this.entityManager.remove(loadAuthorAttached(id));
	}

	@Override
	public void deleteBook(long id) {
		this.entityManager.remove(loadBookAttached(id));
	}

}
